//==============================================================================
//
//         DPNGImage - the PNG image class in the ofc-library
//
//               Copyright (C) 2007  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2007-04-30 13:20:10 $ $Revision: 1.7 $
//
//==============================================================================

#include "ofc/DPNGImage.h"

#include "ofc/DData.h"

#ifdef HAVE_DPNGIMAGE
#include "png.h"
#endif


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DImage.h"
#include "ofc/DDataReadable.h"
#include "ofc/DDataWritable.h"

#if defined(HAVE_LIBPNG) && defined(HAVE_PNG_H)
#define HAVE_DPNGIMAGE 1
#endif

#ifdef HAVE_DPNGIMAGE

//
// The DPNGImage class implements methods for reading and writing PNG images.
// 

@interface DPNGImage : Object <DImage>
{
@private
  void                *_png;           // the png pointer
  void                *_info;          // the png info pointer
  unsigned             _height;        // the height of the image
  unsigned             _width;         // the width of the image
  unsigned             _bpp;           // the bytes per pixel
  unsigned             _images;        // the number of images in the image
  int                  _bbc;           // the bits per color
  int                  _colorType;     // the color type
  int                  _interlaceType; // the interlace type
  BOOL                 _reading;       // reading or writing an image ?
  BOOL                 _error;         // is an error active ?
}

#endif
#endif



#ifdef HAVE_DPNGIMAGE

@implementation DPNGImage

//
// Initialise a PNG image object
//
// @return the object
//
- (DPNGImage *) init
{
  [super init];

  _png           = NULL;
  _info          = NULL;
  _height        = 0;
  _width         = 0;
  _bpp           = 4;
  _bbc           = 0;
  _colorType     = 0;
  _interlaceType = 0;
  _images        = 0;
  _reading       = YES;
  _error         = NO;
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of a PNG image (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");

  return [super shallowCopy];
}


//// Deconstructor

//
// Free the image object
// 
// @return the object
// 
- free
{
  if (_png != NULL)
  {
    [self close];
  }
  
  return [super free];
}



//// Image members

//
// Return the height of the image
// 
// @return the height
// 
- (unsigned) height
{
  return _height;
}

//
// Return the width of the image
// 
// @return the width
// 
- (unsigned) width
{
  return _width;
}

//
// Return the bytes per pixel (3: RGB or 4: RGBA)
// 
// @return the number of bytes
// 
- (unsigned) bytesPerPixel
{
  return _bpp;
}


//
// Return the number of images in the image (interlacing, always 1)
// 
// @return the images
// 
- (unsigned) images
{
  return _images;
}


//// Image methods

//
// Check if an image is an PNG image
// 
// @param source        the source of the image
// 
// @return is it ?
// 
+ (BOOL) isImage :(id <DDataReadable>) source
{
  BOOL image = NO;
  
  if (source == nil)
  {
    WARNING(DW_INVALID_ARG, "source");
  }
  else
  {
    unsigned char data[8];
    unsigned long current = [source tell];
    unsigned long length  = 0;
    
    [source seek :0 :0];
    
    length = [source readData :data :sizeof(data) / sizeof(data[0])];
    
    image = (!png_sig_cmp((png_bytep)data, 0, length));
    
    [source seek :current :0];
  }
  
  return image;
}


#if _PRIVATE_
//
// Callback for reading data from readable
// 
// @param png        the png structure with the object reference
// @param buffer     the destination
// @param size       the length to be read
// 
static void _readData(png_structp png, png_bytep buffer, png_size_t size)
{
  id <DDataReadable> src = (id <DDataReadable>) png_get_io_ptr(png);
  
  [src readData :buffer :size];
}

//
// Callback for writing data to writable
// 
// @param png        the png structure with the object reference
// @param buffer     the source
// @param size       the length to be written
// 
static void _writeData(png_structp png, png_bytep buffer, png_size_t length)
{
  id <DDataWritable> dst = (id <DDataWritable>) png_get_io_ptr(png);
  
  [dst writeData :buffer :length];
}

//
// Callback for flushing a writable
// 
// @param png        the png structure with the object reference
// 
static void _flushData(png_structp png)
{
  id <DDataWritable> dst = (id <DDataWritable>) png_get_io_ptr(png);
  
  [dst flush];
}

static void _warning(png_structp png, png_const_charp msg)
{
  // ignore..
}
#endif

//
// Open the PNG image object for reading
// 
// @param source        the source with the image
//
// @return success
//
- (BOOL) open :(id <DDataReadable>) source
{
  BOOL ok = NO;
  
  if (source == nil)
  {
    WARNING(DW_INVALID_ARG, "source");
  }
  else
  {
    png_structp  png  = NULL;
    png_infop    info = NULL;
    png_uint_32  width;
    png_uint_32  height;
    
    ok     = YES;
    _error = NO;
    
    png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, _warning);
    if (png == NULL)
    {
      WARNING(DW_UNKNOWN_WARNING, "Invalid PNG library");
      ok = NO;
    }
    
    if (ok)
    {
      info = png_create_info_struct(png);
      if (info == NULL)
      {
        WARNING(DW_UNKNOWN_WARNING, "Info structure failed");
        ok = NO;
      }
    }
    
    if (ok)
    {
      if (setjmp(png->jmpbuf))
      {
        WARNING(DW_UNKNOWN_WARNING, "Error reading PNG file");
        ok     = NO;
        _error = YES;
      }
    }
    
    if (ok)
    {
      png_set_read_fn(png, source, _readData);

      png_read_info(png, info);
      
      png_get_IHDR(png, info, &width, &height, &_bbc, &_colorType, &_interlaceType, NULL, NULL);

      if (_interlaceType != PNG_INTERLACE_NONE)
      {
        WARNING(DW_UNKNOWN_WARNING, "DPNGImage does not support interlaced images");
        
        ok = NO;
      }
    }
    
    if (ok)
    {
      // printf("Width: %d Height: %d bpc: %d colorType: %d interfaceType: %d Channels:%d\n", (int) width, (int) height, _bbc, _colorType, _interlaceType, info->channels);

      png_set_expand(png);
      
      if (_bbc == 16)
        png_set_strip_16(png); // convert to 8 bits
      
      if (_bbc < 8)
        png_set_packing(png);
      
      if ((_colorType == PNG_COLOR_TYPE_GRAY) || (_colorType == PNG_COLOR_TYPE_GRAY_ALPHA))
        png_set_gray_to_rgb(png);

      png_read_update_info(png, info);
      png_get_IHDR(png, info, &width, &height, &_bbc, &_colorType, &_interlaceType, NULL, NULL);
      
      // printf("Width: %d Height: %d bpc: %d colorType: %d interfaceType: %d Channels:%d\n", (int) width, (int) height, _bbc, _colorType, _interlaceType, info->channels);

      if (png_get_valid(png, info, PNG_INFO_tRNS))
      {
        png_set_tRNS_to_alpha(png);
      }
      
      if (_colorType == PNG_COLOR_TYPE_RGB)
      {
        png_set_filler(png, 255, PNG_FILLER_AFTER);
      
        png_read_update_info(png, info);
      
        png_get_IHDR(png, info, &width, &height, &_bbc, &_colorType, &_interlaceType, NULL, NULL);
      }
      
      _width  = (unsigned) width;
      _height = (unsigned) height;
      _bpp    = info->channels;
      _images = 1;
      
      _reading = YES;
    }

    _png = png;
    _info = info;

    if (!ok)
    {
      _error = YES;
      
      [self close];
    }
  }
  
  return ok;
}


//
// Open the PNG image object for writing
// 
// @param destination        the file for the image
// @param width              the width of the image
// @param height             the height of the image
// @param bytesPerPixel      the number of bytes per pixel (3 for RGB or 4 for RGBA)
//
// @return succes
//
- (BOOL) open :(id <DDataWritable>) destination :(unsigned) width :(unsigned) height :(unsigned) bytesPerPixel
{
  BOOL ok = NO;
  
  if (destination == nil)
  {
    WARNING(DW_INVALID_ARG, "destination");
  }
  else if ((width == 0) || (height == 0))
  {
    WARNING(DW_INVALID_ARG, "width/height");
  }
  else if ((bytesPerPixel != 3) && (bytesPerPixel != 4))
  {
    WARNING(DW_INVALID_ARG, "bytesPerPixel");
  }
  else
  {
    png_structp  png  = NULL;
    png_infop    info = NULL;
    
    ok     = YES;
    _error = NO;    
    
    if (_png != NULL)
    {
      [self close];
    }
    
    png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, _warning);
    if (png == NULL)
    {
      WARNING(DW_UNKNOWN_WARNING, "Invalid PNG library");
      ok = NO;
    }
    
    if (ok)
    {
      info = png_create_info_struct(png);
      if (info == NULL)
      {
        WARNING(DW_UNKNOWN_WARNING, "Info structure failed");
        ok = NO;
      }
    }
    
    if (ok)
    {
      if (setjmp(png->jmpbuf))
      {
        WARNING(DW_UNKNOWN_WARNING, "Error writing PNG file");
        ok     = NO;
        _error = YES;
      }
    }
    
    if (ok)
    {
      png_set_write_fn(png, destination, _writeData, _flushData);

      png_set_IHDR(png, info, width, height, 8, (bytesPerPixel == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA), 
                   PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
      
      
      png_write_info(png, info);
    }

    if (ok)
    {
      _width  = width;
      _height = height;
      _bpp    = bytesPerPixel;
      _images = 1;
      
      _reading = NO;

      _png = png;
      _info = info;
    }
    else
    {
      _error = YES;
      
      [self close];
    }
  }
  
  return ok;
}


//
// Get the next row of image data from the file (format: RGB... or RGBA...)
// 
// @param data          the image data for the next line (size = _width * _bpp)
//
// @return the number of pixels in the row (or 0 for error)
//
- (unsigned) readRow :(unsigned char *) data
{
  unsigned pixels = 0;
  
  if (data == NULL)
  {
    WARNING(DW_INVALID_ARG, "data");
  }
  else if ((_png == NULL) || (!_reading))
  {
    WARNING(DW_INVALID_STATE, "open:");
  }
  else
  {
    png_structp png = (png_structp) _png;
    
    pixels = _width;
    
    if (setjmp(png->jmpbuf))
    {
      WARNING(DW_UNKNOWN_WARNING, "Error reading PNG file");
      pixels = 0;
      _error = YES;
    }
    
    if (pixels > 0)
    {
      png_read_row(png, data, NULL);
    }
  }
    
  return pixels;
}

//
// Set the next row of image data in the png file (format: RGB.. or RGBA..)
// 
// @param data          the image data for the next line (size = _width * _bpp)
// @param width         the number of pixels in the row
//
// @return success
//
- (BOOL) writeRow :(unsigned char *) data :(unsigned) width
{
  BOOL ok = NO;
  
  if (data == NULL)
  {
    WARNING(DW_INVALID_ARG, "data");
  }
  else if ((_png == NULL) || (_reading))
  {
    WARNING(DW_INVALID_STATE, "open:");
  }
  else if (width != _width)
  {
    WARNING(DW_INVALID_ARG, "width");
  }
  else
  {
    ok = YES;
    
    png_structp png = (png_structp) _png;
    
    if (setjmp(png->jmpbuf))
    {
      WARNING(DW_UNKNOWN_WARNING, "Error writing PNG file");
      ok     = NO;
      _error = YES;
    }
    
    if (ok)
    {
      png_write_row(png, data);
    }
  }
    
  return ok;
}

//
// Close the image object
// 
// @return success
//
- (BOOL) close
{
  if (_png != NULL)
  {
    if (_reading)
    {
      png_structpp png  = (png_structpp) &_png;
      png_infopp   info = (_info != NULL ? (png_infopp)&_info : (png_infopp) 0);
    
      png_destroy_read_struct(png, info, (png_infopp)0);
    }
    else
    {
      if (!_error)
      {
        png_write_end(_png, _info);
      }
      
      png_structpp png  = (png_structpp) &_png;
      png_infopp   info = (_info != NULL ? (png_infopp)&_info : (png_infopp) 0);
    
      png_destroy_write_struct(png, info);
    }
    
    _png           = NULL;
    _info          = NULL;
    _height        = 0;
    _width         = 0;
    _bpp           = 4;
    _bbc           = 0;
    _colorType     = 0;
    _interlaceType = 0;
    _images        = 0;
    _error         = NO;
    
  }
  
  return YES;
}

@end

#endif

/*===========================================================================*/
